﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Net;
using System.Net.Sockets;
using System.Runtime.Serialization.Formatters.Binary;
using System.Text;
using System.Threading;
using System.Windows.Forms;

namespace Tetris
{
    public partial class Tetris : Form
    {
        private PictureBox[] pieces; //picture boxes for the current shape used
        private PictureBox[] nextShape;
        private PictureBox[][] pictures; // picture boxes for the old shapes used (will show their last positions)
        private PictureBox[][] pictures_mini; // picture boxes for the old shapes used (will show their last positions)
        private static int sizeElementShape = 29;
        private static int sizeElementMiniShape = 10;
        private Game game;
        public static Game gameMulti = new Game();
        public delegate void ListenTCP(Game game);
        private TcpSender sender;
        

        
        /// <summary>
        /// Main constructor of the game view
        /// </summary>   
        public Tetris()
        {
            game = new Game();
            InitializeComponent();

            this.KeyDown += new KeyEventHandler(tetris_KeyDown);
            
            initializeShape(game.CurShape);

            initializeNextShapeView(game.NextShape);

            initialiseHiddenShape();
        }

        public void listenServ1()
        {

            TcpListener server = new TcpListener(IPAddress.Any, 50000);
            server.Start();
            TcpClient client = server.AcceptTcpClient();
            NetworkStream stream = client.GetStream();
            BinaryFormatter formatter = new BinaryFormatter();
            while (true)
            {
                Game g = (Game)formatter.Deserialize(stream);
                this.Invoke((ListenTCP)Tetris.UpdateGameMulti, g);
            }
        }

        public void listenServ2()
        {

            TcpListener server = new TcpListener(IPAddress.Any, 50001);
            server.Start();
            TcpClient client = server.AcceptTcpClient();
            NetworkStream stream = client.GetStream();
            BinaryFormatter formatter = new BinaryFormatter();
            while (true)
            {
                Game g = (Game)formatter.Deserialize(stream);
                this.Invoke((ListenTCP)Tetris.UpdateGameMulti, g);
            }
        }

        /// <summary>
        /// Update the gameMulti with what we listened on the network 
        /// </summary>
        /// <param name="game"></param>
        public static void UpdateGameMulti(Game g)
        {
            Tetris.gameMulti = g;
        }
        /// <summary>
        ///  Initialize and print the next shape visualisation
        /// </summary>
        /// 
        public void initializeNextShapeView(Shape shape)
        {
            nextShape = new PictureBox[4];
            for (int i = 0; i < 4; i++)
            {
                this.nextShape[i] = new PictureBox();
                this.nextShape[i].Image = Image.FromFile(shape.Image);
                this.nextShape[i].Location = new System.Drawing.Point((shape.Points[i].X-2) * sizeElementShape, (shape.Points[i].Y+3) * sizeElementShape);
                this.nextShape[i].Size = new System.Drawing.Size(sizeElementShape, sizeElementShape);
                this.nextShape[i].Visible = true;
                this.panelScore.Controls.Add(this.nextShape[i]);
            }
        }
        
        /// <summary>
        /// Initialize all the hidden shapes that will be used to print out the location of the old shapes used 
        /// </summary>
        public void initialiseHiddenShape()
        {
            pictures = new PictureBox[Game.NBCOLUMN-2][];
            for (int i = 0; i < Game.NBCOLUMN - 2; i++)
            {
                pictures[i] = new PictureBox[Game.NBROW-4];
                for (int j = 0; j < Game.NBROW-4; j++)
                {
                    pictures[i][j] = new PictureBox();
                    pictures[i][j].Image = Image.FromFile("../../../images/teal.png");
                    pictures[i][j].Location = new System.Drawing.Point(i * sizeElementShape, j * sizeElementShape);
                    pictures[i][j].Size = new System.Drawing.Size(sizeElementShape, sizeElementShape);
                    this.panelGame.Controls.Add(this.pictures[i][j]);
                    pictures[i][j].Visible = false;
                }
            }
            pictures_mini = new PictureBox[Game.NBCOLUMN - 2][];
            for (int i = 0; i < Game.NBCOLUMN - 2; i++)
            {
                pictures_mini[i] = new PictureBox[Game.NBROW - 4];
                for (int j = 0; j < Game.NBROW - 4; j++)
                {
                    pictures_mini[i][j] = new PictureBox();
                    pictures_mini[i][j].Image = Image.FromFile("../../../images/teal_mini.png");
                    pictures_mini[i][j].Location = new System.Drawing.Point(i * sizeElementMiniShape, j * sizeElementMiniShape);
                    pictures_mini[i][j].Size = new System.Drawing.Size(sizeElementMiniShape, sizeElementMiniShape);
                    this.miniView.Controls.Add(this.pictures_mini[i][j]);
                    pictures_mini[i][j].Visible = false;

                }
            }
        }
        /// <summary>
        /// THis function update the view according to the data in the plateau
        /// </summary>
        public void updatePlateau()
        {
            if (game!=null)
            {
                for (int i = 0; i < Game.NBCOLUMN - 2; i++)
                {
                    for (int j = 0; j < Game.NBROW - 4; j++)
                    {
                        pictures[i][j].Visible = !game.Plateau[i+1][j+3].Available;
                    }
                }
            }
        }
        public void updateMultiView(Game g)
        {
            if (g != null)
            {
                for (int i = 0; i < Game.NBCOLUMN - 2; i++)
                {
                    for (int j = 0; j < Game.NBROW - 4; j++)
                    {
                        pictures_mini[i][j].Visible = !g.Plateau[i+1][j+3].Available;
                    }
                }
            }
        }
        /// <summary>
        /// Initialize the current shape
        /// </summary>
        /// <param name="shape">Current Shape</param>
        public void initializeShape(Shape shape)
        {
            pieces = new PictureBox[4];
            for (int i = 0; i < 4; i++)
            {
                this.pieces[i] = new PictureBox();
                this.pieces[i].Image = Image.FromFile(shape.Image);
                this.pieces[i].Location = new System.Drawing.Point((shape.Points[i].X - 1) * sizeElementShape, (shape.Points[i].Y - 3) * sizeElementShape);
                this.pieces[i].Size = new System.Drawing.Size(sizeElementShape, sizeElementShape);
                this.panelGame.Controls.Add(this.pieces[i]);
            }
        }

       /// <summary>
        /// Function used by the timer each tick (also the game loop)
       /// </summary>
       /// <param name="sender"></param>
       /// <param name="e"></param>
        public void timer_Tick(object sender, EventArgs e)
        {
            if (game.isEnd())
            {
                this.timer.Stop();
                this.clock.Stop();
                System.Windows.Forms.MessageBox.Show("GAME OVER !\nSCORE : "+game.Score);
                game = new Game();
                this.timer.Start();
                this.clock.Start();
            }
            if (game.CurShape.canMove("down", this.game)) //shape can move down
            {
                game.CurShape.moveDown(game);
            }
            else //prepare the next shape
            {
                game.CurShape.setOnPlateau(game);
                game.CurShape = game.NextShape;
                game.NextShape = game.randomShape();
                this.updateNextShapePreview();

                //check if they have completed rows in the game and delete them
                while (game.checkRow() != 0)
                {
                    game.ConsecutiveRows++;
                    game.deleteRow(game.checkRow());
                    game.NbRows= (game.NbRows+1);
                    if ((game.NbRows % 10) == 0)
                    {
                        game.Level+=1;
                        this.timer.Interval = (int)(this.timer.Interval * 0.85);
                        this.level.Text = game.Level.ToString();
                    }
                }
                game.Score = (game.Score + game.ConsecutiveRows * game.Level * 10);
            }
            this.setShape(); //set the shape on the game
            this.score.Text = game.Score.ToString();
            this.systemTimeValue.Text = SystemTime.getSystemTime(); //print out the current hour of the system
            this.updatePlateau(); //update the plateau if needed
            this.updateMultiView(gameMulti);
            game.ConsecutiveRows = 0;
            if(this.game.isInMultigame)
                this.sender.formatter.Serialize(this.sender.stream, game);
        }

        /// <summary>
        /// Function used to calculate and print the duration of the game
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        public void clock_Tick(object sender, EventArgs e)
        {
            game.setClock();
            this.clockValue.Text = game.printClock();
        }

        /// <summary>
        /// Function for the keyboard interactions
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        public void tetris_KeyDown(object sender, KeyEventArgs e)
        {
            startPauseGame(e);
            setShapeDirection(e);
        }

        /// <summary>
        /// Stop or Start the game timer
        /// </summary>
        /// <param name="e"></param>
        public void startPauseGame(KeyEventArgs e)
        {
            if (e.KeyData == Keys.Space) //pause/start game
            {
                if (!this.timer.Enabled)
                {
                    this.timer.Start();
                    this.clock.Start();
                }
                else
                {
                    this.timer.Stop();
                    this.clock.Stop();
                    System.Windows.Forms.MessageBox.Show("GAME PAUSED !");
                    this.timer.Start();
                    this.clock.Start();
                }
            }
        }

        /// <summary>
        /// Deal with all the possible directions for the shape
        /// </summary>
        /// <param name="e"></param>
        public void setShapeDirection(KeyEventArgs e)
        {
            if (this.timer.Enabled)
            {
                if (e.KeyData == Keys.Q) //left
                {
                    if (game.CurShape.canMove("left", this.game))
                    {
                        game.CurShape.moveLeft(game);
                        setShape();
                    }
                }
                if (e.KeyData == Keys.D) //right
                {
                    if (game.CurShape.canMove("right", this.game))
                    {
                        game.CurShape.moveRight(game);
                        setShape();
                    }
                }
                if (e.KeyData == Keys.P) //right rotation
                {
                    game.CurShape.rotate(Shape.CLOCKWISE);
                    if (!game.CurShape.canBeHere(game))
                    {
                        game.CurShape.rotate(Shape.COUNTCLOCKWISE);
                    }
                    setShape();
                }
                if (e.KeyData == Keys.O) //left rotation
                {
                    game.CurShape.rotate(Shape.COUNTCLOCKWISE);
                    if (!game.CurShape.canBeHere(game))
                    {
                        game.CurShape.rotate(Shape.CLOCKWISE);
                    }
                    setShape();
                }
                if (e.KeyData == Keys.S) //down
                {
                    if (game.CurShape.canMove("down", this.game))
                    {
                        game.CurShape.moveDown(game);
                    }
                    else
                    {
                        game.CurShape.setOnPlateau(game);
                    }
                    setShape();
                }
            }
        }

        /// <summary>
        /// Draw the current shape
        /// </summary>
        public void setShape()
        {
            for (int i = 0; i < 4; i++)
            {
                this.pieces[i].Location = new System.Drawing.Point((game.CurShape.Points[i].X - 1) * sizeElementShape, (game.CurShape.Points[i].Y - 3) * sizeElementShape);
            }
        }

        /// <summary>
        /// Update the next Shape Preview
        /// </summary>
        public void updateNextShapePreview()
        {
            for (int i = 0; i < 4; i++)
            {
                this.nextShape[i].Location = new System.Drawing.Point((game.NextShape.Points[i].X - 2) * sizeElementShape, (game.NextShape.Points[i].Y + 3) * sizeElementShape);
            }
        }

        /// <summary>
        /// Used to create a new game
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void newGameToolStripMenuItem_Click(object sender, EventArgs e)
        {
            game = new Game();
        }

        /// <summary>
        /// Quit the game
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void quitToolStripMenuItem_Click(object sender, EventArgs e)
        {
             this.Close();
        }

        /// <summary>
        /// Save the current game
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void saveToolStripMenuItem_Click(object sender, EventArgs e)
        {
            Serialization.Serialize(game);
        }

        /// <summary>
        /// load the saved game
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void loadToolStripMenuItem_Click(object sender, EventArgs e)
        {
            game = Serialization.Deserialize();
        }

        private void serverToolStripMenuItem_Click(object sender, EventArgs e)
        {
            if(this.NtwkValue.Text.Equals("")){
            this.NtwkValue.Text = "1";
            //listen thread
            Thread listenThread = new Thread(new ThreadStart(listenServ1));
            listenThread.IsBackground = true;
            listenThread.Start();
            }
            this.serverToolStripMenuItem.Enabled = false;
            this.clientToolStripMenuItem.Enabled = false;
            
        }

        private void clientToolStripMenuItem_Click(object sender, EventArgs e)
        {
                this.NtwkValue.Text = "2";
                //listen thread
                Thread listenThread = new Thread(new ThreadStart(listenServ2));
                listenThread.IsBackground = true;
                listenThread.Start();
                this.clientToolStripMenuItem.Enabled = false;
                this.serverToolStripMenuItem.Enabled = false;
        }

        private void gOToolStripMenuItem_Click(object sender, EventArgs e)
        {
            this.game.isInMultigame = true;
            if (this.NtwkValue.Text.Equals("1"))
            {
                this.sender = new TcpSender(TcpSender.getIp(), 50001);
            }
            else
                this.sender = new TcpSender(TcpSender.getIp(), 50000);
            this.gOToolStripMenuItem.Enabled = false;
        }

        private void controlToolStripMenuItem_Click(object sender, EventArgs e)
        {
            this.timer.Stop();
            this.clock.Stop();
            System.Windows.Forms.MessageBox.Show("Press O and P to rotate the Shape\nPress Q and D to move from right to left. To scroll down the shape, Press S");
            this.clock.Start();
            this.timer.Start();

        }

        private void networkToolStripMenuItem1_Click(object sender, EventArgs e)
        {
            this.timer.Stop();
            this.clock.Stop();
            System.Windows.Forms.MessageBox.Show("For the network, we have implemented a full duplex communication.\nTo be able to use the newtork, please follow the following steps:\n" +
                "1-Start the server connection of your first application by clicking on 'Player 1' under the network tab\n" +
                "2-Start a second application of this same game and start the server of the second Application by clicking on 'Player 2'\n" +
                "3-Start both clients which are going to send serialized object to the Server 1 and Server 2 by clicking on 'Go' button on both forms\n" +
                "Each form should now be seeing the other player state of the game in the preview on the right of the winform");
            this.clock.Start();
            this.timer.Start();
        }
    }
}
